package org.esti.controller;

import org.esti.model.Appraisal;
import org.esti.model.AppraisalTask;
import org.esti.model.KnowledgeField;
import org.esti.model.User;
import org.esti.model.exceptions.AppraisalDoesNotExistException;
import org.esti.model.utils.AppraisalPropertyEditor;
import org.esti.model.utils.KnowledgeFieldPropertyEditor;
import org.esti.model.utils.LevelPropertyEditor;
import org.esti.service.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * @author Artem Loginov (logart)
 */

/**
 * This controller is used for providing mapping between request and view
 * and showing different appraisal information.
 * For example: is this appraisal publish or not or date of it's creation.
 */
@Controller
public class AppraisalController {

    private static final String ADD_APPRAISAL_VIEW_NAME = "add_appraisal";
    private static final String ADD_ACTION = "action=add";
    private static final Logger LOGGER = Logger.getLogger(AppraisalController.class.getName());

    @Autowired
    private UserService userService;

    @Autowired
    private AppraisalTaskService appraisalTaskService;

    @Autowired
    private AppraisalService appraisalService;

    @Autowired
    private KnowledgeFieldService knowledgeFieldService;

    @Autowired
    private LevelService levelService;

    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        binder.registerCustomEditor(org.esti.model.Appraisal.class, new AppraisalPropertyEditor(appraisalService));
        binder.registerCustomEditor(org.esti.model.Level.class, new LevelPropertyEditor(levelService));
        binder.registerCustomEditor(org.esti.model.KnowledgeField.class, new KnowledgeFieldPropertyEditor(knowledgeFieldService));
        CustomDateEditor editor = new CustomDateEditor(new SimpleDateFormat("dd/MM/yyyy H:mm"), true);
        binder.registerCustomEditor(Date.class, editor);
    }

    /**
     * handle excaption when appraisal is not found
     *
     * @param ex      exception which was thrown
     * @param request which contain information about what address was loaded
     * @return return error view
     */
    @ExceptionHandler(value = AppraisalDoesNotExistException.class)
    public ModelAndView handleNoAppraisalException(AppraisalDoesNotExistException ex, HttpServletRequest request) {
        LOGGER.log(Level.INFO, ex.toString());
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("exception");
        modelAndView.getModel().put("message", "There are any published appraisal for your level. Please wait while administrator will create ones.");
        return modelAndView;
    }

    /**
     * show all available appraisals
     *
     * @param model contain information needed by view resolver to parse velocity
     * @return view name
     */
    @RequestMapping(value = "/appraisals")
    public String showAppraisals(Model model) {
        List<Appraisal> appraisals = appraisalService.getAllAppraisals();
        model.addAttribute("appraisals", appraisals);
        return "appraisals";
    }

    /**
     * show form to edit appraisal
     *
     * @param id    appraisal id which will be edited
     * @param model contain information needed by view resolver to parse velocity
     * @return view name
     */
    @RequestMapping(value = "/appraisals/{id}", params = "action=edit", method = RequestMethod.GET)
    public String showEditAppraisalsForm(@PathVariable long id, Model model) {
        Appraisal appraisal = appraisalService.getAppraisalById(id);
        Map<Long, String> levels = levelService.getAllLevelsMap();

        List<AppraisalTask> appraisalTasks = appraisal.getAppraisalTasks();

        Map<KnowledgeField, List<String>> tasksByKnowledge = new HashMap<KnowledgeField, List<String>>();

        for (AppraisalTask task : appraisalTasks) {
            if (tasksByKnowledge.containsKey(task.getKnowledgeField())) {
                tasksByKnowledge.get(task.getKnowledgeField()).add(task.getSubject());
            } else {
                List<String> appraisalTasksByKnowledge = new ArrayList<String>();
                appraisalTasksByKnowledge.add(task.getSubject());
                tasksByKnowledge.put(task.getKnowledgeField(), appraisalTasksByKnowledge);
            }
        }

        model.addAttribute("appraisalTasks", tasksByKnowledge);
        model.addAttribute("appraisalTaskCount", appraisalTasks.size());


        model.addAttribute(appraisal);
        model.addAttribute("levels", levels);
        model.addAttribute("fieldsOfKnowledges", knowledgeFieldService.getKnowledgeFieldMap());
        return ADD_APPRAISAL_VIEW_NAME;
    }

    /**
     * process edit appraisal form
     *
     * @param appraisal     with updated field which will be saved to db
     * @param bindingResult binder that provide validation and error reporting if appraisal has failed fields
     * @param model         contain information needed by view resolver to parse velocity
     * @return view name
     */
    @RequestMapping(value = "/appraisals/{id}", method = RequestMethod.POST)
    public String modifyAppraisal(@ModelAttribute("appraisal") Appraisal appraisal,
                                  BindingResult bindingResult,
                                  Model model) {
        if (bindingResult.hasErrors()) {
            LOGGER.log(Level.INFO, "error till binding appraisal");
            return ADD_APPRAISAL_VIEW_NAME;
        }

        appraisal.setModificationDate(new Date());

        appraisalService.updateAppraisal(appraisal);

        return "redirect:/appraisals";
    }

    /**
     * process removing appraisal
     *
     * @param id of appraisal which will be removed
     * @return view name
     */
    @RequestMapping(value = "/appraisals/{id}", params = "action=delete", method = RequestMethod.GET)
    public String removeAppraisal(@PathVariable long id) {
        appraisalService.delete(id);
        return "redirect:/appraisals";
    }

    /**
     * show add appraisal form
     *
     * @param model contain information needed by view resolver to parse velocity
     * @return view name
     */
    @RequestMapping(value = "/appraisals", params = ADD_ACTION, method = RequestMethod.GET)
    public String showAddAppraisalsForm(Model model) {
        Appraisal appraisal = new Appraisal();
        Map<Long, String> levels = levelService.getAllLevelsMap();

        appraisal.setCreationDate(new Date());

        model.addAttribute(appraisal);
        model.addAttribute("levels", levels);
        model.addAttribute("fieldsOfKnowledges", knowledgeFieldService.getKnowledgeFieldMap());

        return ADD_APPRAISAL_VIEW_NAME;
    }

    /**
     * process adding appraisal
     *
     * @param appraisal     which will be persist to DB
     * @param bindingResult binder that provide validation and error reporting if appraisal has failed fields
     * @param model         contain information needed by view resolver to parse velocity
     * @return view name
     */
    @RequestMapping(value = "/appraisals", params = ADD_ACTION, method = RequestMethod.POST)
    public String addAppraisal(
            @Valid @ModelAttribute Appraisal appraisal,
            BindingResult bindingResult,
            Model model) {

        if (bindingResult.hasErrors()) {
            LOGGER.log(Level.INFO, "error till binding appraisl");

            return ADD_APPRAISAL_VIEW_NAME;
        }

        appraisalService.createAppraisal(appraisal);

        return "redirect:/appraisals";
    }

    /**
     * show all knowledge fields
     *
     * @param model contain information needed by view resolver to parse velocity
     * @return view name
     */
    @RequestMapping(value = "/knowledge-fields")
    public String showKnowledgeFields(Model model) {

        List<KnowledgeField> knowledgeFields = knowledgeFieldService.getAll();
        model.addAttribute("knowledge_fields", knowledgeFields);

        return "knowledge_fields";
    }

    /**
     * show add knowledge field form
     *
     * @param model contain information needed by view resolver to parse velocity
     * @return view name
     */
    @RequestMapping(value = "/knowledge-fields", params = ADD_ACTION, method = RequestMethod.GET)
    public String showAddKnowledgeFieldForm(Model model) {

        model.addAttribute("knowledge_field", new KnowledgeField());

        return "add_knowledge_field";
    }

    /**
     * process add knowledge field form
     *
     * @param knowledgeField which will be saved to DB
     * @param model contain information needed by view resolver to parse velocity
     * @return view name
     */
    @RequestMapping(value = "/knowledge-fields", params = ADD_ACTION, method = RequestMethod.POST)
    public String processAddKnowledgeFieldForm(
            @Valid KnowledgeField knowledgeField,
            BindingResult bindingResult, Model model) {

        if (bindingResult.hasErrors()) {
            LOGGER.log(Level.INFO, "registration form is not valid");
            return "redirect:/";
        }

        knowledgeFieldService.add(knowledgeField);

        return "redirect:/knowledge-fields?action=add";
    }

    /**
     * process removing knowledge filed
     *
     * @param id of knowledge field which will be removed
     * @return view name
     */
    @RequestMapping(value = "/knowledge-fields/{id}", params = "action=delete")
    public String removeKnowledgeFieldForm(
            @PathVariable long id
    ) {

        knowledgeFieldService.remove(id);

        return "redirect:/knowledge-fields";
    }


    /**
     * Show current appraisal for user.
     * Current appraisal is topics that user must know to getKnowledgeFieldById "next level" of education.
     *
     * @param login of user whose appraisal we want to show
     * @param model set of parameters which will be passed to view. For example user and his appraisal.
     * @return name of view which will be render
     */
    @RequestMapping(value = "/{login}/appraisals/current")
    public String showCurrentAppraisal(@PathVariable String login, Model model) {
        User user = userService.getUserByLogin(login);

        model.addAttribute("user", user);

        Map<KnowledgeField, List<AppraisalTask>> appraisalTasks = appraisalTaskService.getAppraisalTasksByLevelId(user.getLevel().getId());

        model.addAttribute("appraisalTasks", appraisalTasks);

        return "current_appraisal";
    }
}
